/**
 * Title :	Property view
 * Description: Property editor
 * Version:	provider
 * Copyright (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package bsys.propertyview;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;

/**
 * @author bbou
 *
 */
/**
 * @author bbou
 *
 */
/**
 * @author bbou
 */
public class PropertyView extends JPanel implements PickListener
{
	private static final long serialVersionUID = 1L;

	/**
	 * String for default value
	 */
	static final String theDefaultString = Messages.getString("PropertyView.0"); //$NON-NLS-1$

	// I N T E R F A C E S

	/**
	 * Attribute type
	 */
	public static enum AttributeType
	{
		NONE, BOOLEAN, INTEGER, DOUBLE, LABEL, TEXT, LONGTEXT, COLOR, ID, REFID, IMAGE, LINK, FONTFACE, FONTSIZE, STROKE, TERMINATOR
	};

	/**
	 * Set attribute value interface
	 */
	public static interface Setter
	{
		public void set(Object thisObject, String thisAttributeName, Object thisAttributeValue);
	}

	/**
	 * Get attribute value interface
	 */
	public static interface Getter
	{
		public Object get(Object thisObject, String thisAttributeName);
	}

	/**
	 * Get object referenced by id interface
	 */
	public static interface IdGetter
	{
		/**
		 * Get object referenced by id interface
		 * 
		 * @param thisId
		 *        id
		 * @return object referenced by id
		 */
		public Object get(String thisId);

		/**
		 * Get set of ids
		 * 
		 * @return set of ids
		 */
		public Set<String> ids();
	}

	/**
	 * Attribute descriptor
	 * 
	 * @author Bernard Bou
	 */
	public static class AttributeDescriptor
	{
		/**
		 * Name
		 */
		public String theName;

		/**
		 * Type
		 */
		public AttributeType theType;

		/**
		 * Whether attribute value can be changed
		 */
		public boolean isReadOnly;

		/**
		 * Whether the attribute is mandatory in object description
		 */
		public boolean isMandatory;

		/**
		 * Rank value used in sorting
		 */
		public int theRank;

		/**
		 * Possible values
		 */
		public String[] thePossibleValues;
	}

	/**
	 * Attribute (descriptor and value)
	 * 
	 * @author Bernard Bou
	 */
	public static class Attribute
	{
		/**
		 * Descriptor
		 */
		public AttributeDescriptor theDescriptor;

		/**
		 * Value
		 */
		public Object theValue;

		/**
		 * Constructor
		 * 
		 * @param thisDescriptor
		 *        descriptor
		 */
		public Attribute(AttributeDescriptor thisDescriptor)
		{
			theDescriptor = thisDescriptor;
		}
	}

	/**
	 * Interface giving access to an object's attributes
	 * 
	 * @author Bernard Bou
	 */
	public static class Handler
	{
		/**
		 * Set of attributes descriptors
		 */
		public Set<AttributeDescriptor> theAttributeDescriptors;

		/**
		 * Set routines
		 */
		public Setter theSetter;

		/**
		 * Get routines
		 */
		public Getter theGetter;

		/**
		 * Access to id map
		 */
		public IdGetter theIdGetter;
	}

	/**
	 * Get handler for this object
	 * 
	 * @author Bernard Bou
	 */
	public static interface HandlerFactory
	{
		/**
		 * Get handler for this object
		 * 
		 * @param thisObject
		 * @return handler for this object
		 */
		public Handler create(Object thisObject);
	}

	// D A T A

	/**
	 * Change notifier
	 */
	public ChangeNotifier theChangeNotifier = new ChangeNotifier();

	/**
	 * Handler factory
	 */
	protected HandlerFactory theHandlerFactory;

	/**
	 * Image repository URL
	 */
	protected URL theImageRepository;

	// components

	/**
	 * Table
	 */
	protected final JTable theAttributeTable = new JTable();

	// renderer and editor

	/**
	 * Table renderer
	 */
	private Renderer theAttributeCellRenderer = new Renderer(this);

	/**
	 * Table editor
	 */
	private Editor theAttributeCellEditor = new Editor(this);

	// table columns

	static final int STATUS = 0;

	static final int TYPE = 1;

	static final int NAME = 2;

	static final int VALUE = 3;

	private static final int COLUMNNUMBER = VALUE + 1;

	// strings and icons

	/**
	 * Stoke strings
	 */
	static public final String theStrokeStrings[] = { "solid", "dash", "dot" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

	/**
	 * String to stroke icons
	 */
	static public final Map<String, ImageIcon> theStrokeIcons = new HashMap<String, ImageIcon>();

	/**
	 * Terminator strings
	 */
	static public final String theTerminatorStrings[] = { "z", "a", "t", "h", "tf", "c", "cf", "d", "df" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$

	/**
	 * String to terminator icon map
	 */
	static public final Map<String, ImageIcon> theTerminatorIcons = new HashMap<String, ImageIcon>();

	/**
	 * Initialize maps
	 */
	static
	{
		theTerminatorIcons.put("z", new ImageIcon(PropertyView.class.getResource("images/terminatorz.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("a", new ImageIcon(PropertyView.class.getResource("images/terminatora.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("h", new ImageIcon(PropertyView.class.getResource("images/terminatorh.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("t", new ImageIcon(PropertyView.class.getResource("images/terminatort.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("tf", new ImageIcon(PropertyView.class.getResource("images/terminatortf.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("c", new ImageIcon(PropertyView.class.getResource("images/terminatorc.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("cf", new ImageIcon(PropertyView.class.getResource("images/terminatorcf.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("d", new ImageIcon(PropertyView.class.getResource("images/terminatord.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theTerminatorIcons.put("df", new ImageIcon(PropertyView.class.getResource("images/terminatordf.png"))); //$NON-NLS-1$ //$NON-NLS-2$

		theStrokeIcons.put("solid", new ImageIcon(PropertyView.class.getResource("images/solid.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theStrokeIcons.put("dash", new ImageIcon(PropertyView.class.getResource("images/dash.png"))); //$NON-NLS-1$ //$NON-NLS-2$
		theStrokeIcons.put("dot", new ImageIcon(PropertyView.class.getResource("images/dot.png"))); //$NON-NLS-1$ //$NON-NLS-2$
	}

	// C O N S T R U C T

	/**
	 * Constructor
	 */
	public PropertyView()
	{
		theHandlerFactory = null;

		initialize();

		// attributes
		theAttributeTable.setRowHeight(24);

		// model
		theAttributeTable.setModel(new TableModel());

		// renderer and editor
		setupColumns();
		setRenderer();
		setEditor(null);
	}

	/**
	 * Initialize
	 */
	private void initialize()
	{
		theAttributeTable.setToolTipText(null);
		JLabel theAttributesLabel = new JLabel(Messages.getString("PropertyView.3")); //$NON-NLS-1$
		this.setPreferredSize(new Dimension(200, 0));
		this.setLayout(new BorderLayout());
		this.add(theAttributesLabel, BorderLayout.NORTH);
		this.add(new JScrollPane(theAttributeTable), BorderLayout.CENTER);
	}

	/**
	 * Set up columns
	 */
	private void setupColumns()
	{
		TableColumn thisStatusColumn = theAttributeTable.getColumnModel().getColumn(STATUS);
		thisStatusColumn.setMaxWidth(17);

		TableColumn thisTypeColumn = theAttributeTable.getColumnModel().getColumn(TYPE);
		thisTypeColumn.setMaxWidth(17);

		TableColumn thisNameColumn = theAttributeTable.getColumnModel().getColumn(NAME);
		thisNameColumn.setMaxWidth(100);
	}

	/**
	 * Set handler factory
	 * 
	 * @param thisHandlerFactory
	 *        handler factory
	 */
	public void setHandlerFactory(HandlerFactory thisHandlerFactory)
	{
		theHandlerFactory = thisHandlerFactory;
	}

	/**
	 * Set renderer
	 */
	private void setRenderer()
	{
		TableColumn thisStatusColumn = theAttributeTable.getColumnModel().getColumn(STATUS);
		thisStatusColumn.setCellRenderer(theAttributeCellRenderer);

		TableColumn thisTypeColumn = theAttributeTable.getColumnModel().getColumn(TYPE);
		thisTypeColumn.setCellRenderer(theAttributeCellRenderer);

		TableColumn thisNameColumn = theAttributeTable.getColumnModel().getColumn(NAME);
		thisNameColumn.setCellRenderer(theAttributeCellRenderer);

		TableColumn thisValueColumn = theAttributeTable.getColumnModel().getColumn(VALUE);
		thisValueColumn.setCellRenderer(theAttributeCellRenderer);
	}

	/**
	 * Set editor
	 * 
	 * @param thisHandler
	 *        handler
	 */
	private void setEditor(Handler thisHandler)
	{
		theAttributeCellEditor.setHandler(thisHandler);

		TableColumn thisValueColumn = theAttributeTable.getColumnModel().getColumn(VALUE);
		thisValueColumn.setCellEditor(theAttributeCellEditor);
	}

	/**
	 * Set editor listener
	 * 
	 * @param thisListener
	 *        listener
	 */
	public void setCellEditorListener(CellEditorListener thisListener)
	{
		theAttributeCellEditor.addCellEditorListener(thisListener);
	}

	/**
	 * Set image repository url
	 * 
	 * @param thisImageRepository
	 *        image repository url
	 */
	public void setImageRepository(URL thisImageRepository)
	{
		theImageRepository = thisImageRepository;
	}

	/**
	 * Get image repository
	 * 
	 * @return image repository url
	 */
	public URL getImageRepository()
	{
		return theImageRepository;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see bsys.propertyview.PickListener#picked(java.lang.Object)
	 */
	public void picked(Object thisObject)
	{
		// model
		Handler thisHandler = theHandlerFactory.create(thisObject);
		javax.swing.table.TableModel thisModel = new TableModel(thisObject, thisHandler);
		theAttributeTable.setModel(thisModel);

		// renderer and editor
		setupColumns();
		setRenderer();
		setEditor(thisHandler);
	}

	// H E L P E R S

	/**
	 * Decode encoded URL (for display)
	 * 
	 * @param thisString
	 *        encode URL string
	 * @return decoded URL string
	 */
	static String decode(String thisString)
	{
		try
		{
			return URLDecoder.decode(thisString, "UTF8"); //$NON-NLS-1$
		}
		catch (UnsupportedEncodingException e)
		{
			System.err.println("Can't decode " + thisString + " - " + e); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return thisString;
	}

	/**
	 * Encode encoded URL
	 * 
	 * @param thisString
	 *        encode URL string
	 * @return decoded URL string
	 */
	static String encode(String thisString)
	{
		try
		{
			return URLEncoder.encode(thisString, "UTF8"); //$NON-NLS-1$
		}
		catch (UnsupportedEncodingException e)
		{
			System.err.println("Cant decode " + thisString + " - " + e); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return thisString;
	}

	// A T T R I B U T E M O D E L

	/**
	 * Table model
	 * 
	 * @author Bernard Bou
	 */
	private class TableModel extends AbstractTableModel
	{
		private static final long serialVersionUID = 1L;

		/**
		 * The picked object
		 */
		private Object thePickedObject;

		/**
		 * Its handler
		 */
		public Handler theHandler;

		/**
		 * Its attributes
		 */
		private Vector<Attribute> theAttributes;

		/**
		 * Constructor
		 */
		public TableModel()
		{
			thePickedObject = null;
			theHandler = null;
		}

		/**
		 * Constructor
		 * 
		 * @param thisObject
		 *        picked object
		 * @param thisHandler
		 *        its handler
		 */
		public TableModel(Object thisObject, Handler thisHandler)
		{
			thePickedObject = thisObject;
			theHandler = thisHandler;
			get();
		}

		/**
		 * Get all attribute values
		 */
		public void get()
		{
			if (thePickedObject == null || theHandler == null)
				theAttributes = null;
			else
			{
				theAttributes = new Vector<Attribute>();
				for (AttributeDescriptor thisAttributeDescriptor : theHandler.theAttributeDescriptors)
				{
					Attribute thisAttribute = new Attribute(thisAttributeDescriptor);
					thisAttribute.theValue = theHandler.theGetter.get(thePickedObject, thisAttribute.theDescriptor.theName);
					theAttributes.add(thisAttribute);
				}
			}
		}

		/**
		 * Set all attribute values
		 */
		public void set()
		{
			if (thePickedObject == null)
				return;
			else
			{
				for (Attribute thisAttribute : theAttributes)
				{
					theHandler.theSetter.set(thePickedObject, thisAttribute.theDescriptor.theName, thisAttribute.theValue);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.TableModel#getRowCount()
		 */
		public int getRowCount()
		{
			if (theAttributes == null)
				return 0;
			return theAttributes.size();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.TableModel#getColumnCount()
		 */
		public int getColumnCount()
		{
			return COLUMNNUMBER;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
		 */
		public boolean isCellEditable(int y, int x)
		{
			if (x == VALUE)
			{
				Attribute thisRow = theAttributes.elementAt(y);
				return !thisRow.theDescriptor.isReadOnly;
			}
			return false;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.TableModel#getValueAt(int, int)
		 */
		public Object getValueAt(int y, int x)
		{
			Attribute thisAttribute = theAttributes.elementAt(y);
			switch (x)
			{
			case STATUS:
				return new Boolean(thisAttribute.theDescriptor.isMandatory);
			case TYPE:
				return thisAttribute.theDescriptor.theType;
			case NAME:
				return thisAttribute.theDescriptor.theName;
			case VALUE:
				return thisAttribute;
			default:
				return null;
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
		 */
		public void setValueAt(Object thisValue, int y, int x)
		{
			if (x == VALUE)
			{
				Attribute thisAttribute = theAttributes.elementAt(y);
				thisAttribute.theValue = thisValue;
				set();
				theChangeNotifier.fireStateChanged(new ChangeEvent(thisAttribute));
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.table.AbstractTableModel#getColumnName(int)
		 */
		public String getColumnName(int x)
		{
			switch (x)
			{
			case STATUS:
				return "x"; //$NON-NLS-1$
			case TYPE:
				return "t"; //$NON-NLS-1$
			case NAME:
				return Messages.getString("PropertyView.1"); //$NON-NLS-1$
			case VALUE:
				return Messages.getString("PropertyView.2"); //$NON-NLS-1$
			default:
				return ""; //$NON-NLS-1$
			}
		}
	}
}
